//CSE 141 Spring 2012
//Matthew Huber and Henry Lin

import java.util.*;
import java.lang.*;
import java.io.*;


class LabelEntry{
	String labelName;
	int labelAddress;
	
}

class Label
{
	int j;
	ArrayList<LabelEntry> labelTable;

	public Label() {
		
		j = 0;
		labelTable = new ArrayList<LabelEntry>();
		
	}

	//add LabelEntry element into labelTable
	public void addLabel(String lName, int lAddr) {

		LabelEntry newLabel = new LabelEntry();
		newLabel.labelName = lName;
		newLabel.labelAddress = lAddr;

		labelTable.add(newLabel);
	}
	
	//returns the label name at lAddr. If lAddr isn't in table, return -1
	public int findLabelAddr(String name){
		for(j=0;j<labelTable.size();j++){
			if(name.equals(labelTable.get(j).labelName))
				return labelTable.get(j).labelAddress;
		}
		return -1;
	}
	
}


public class Assembler_MH extends Assembler{


	public Assembler_MH() {
		labelTable = new Label();
		}
	

	public Assembler_MH(String[] args) throws IOException{
		sourceFile = new BufferedReader(new FileReader(args[0]));
    	out_code = new BufferedWriter(new FileWriter(args[1]+"_i.coe"));
    	out_data = new BufferedWriter(new FileWriter(args[1]+"_d.coe"));
		labelTable = new Label();
	}
	
		
	private Label labelTable;
	private HashMap<String,String> opcodeTable;
	private HashMap<String,String> regTable;
	
	/* The number of lines scanned */
    int currentSourceCodeline = 0;
	
	//the memory address at the current label

    // The student has to implement it for processing the labels.
    public void processLabel(String sourceCode){
		String temp = "";	
		if (isLabel(sourceCode)){
			temp = this.extractLabel(sourceCode);
			labelTable.addLabel(temp, this.programCounter);
		}
	}

    // The student has to implement it for generating the machine codes.
    public String generateCode(Instruction instruction){
			String code = "00";
			int operandsLength,dest;
			String operandType;
			String tempString;
			long immLong;
			long tempLong;
			String offsetString = "";
			boolean isOffset = false;
			int offsetInt = 0;
			
			//concat opcode
			code = code.concat(opcodeTable.get(instruction.operator));

			operandsLength = instruction.operands.length;
			for(int i = 0;i<operandsLength;i++){

				//System.out.println("name: "+instruction.operands[i].name);
				//System.out.println("length: "+instruction.operands.length);
				//System.out.println("operator: "+instruction.operator);

				offsetInt = instruction.operands[i].offset;

				if(instruction.operator.equals("lw") || instruction.operator.equals("sw"))
					isOffset = true;


				operandType = instruction.operands[i].getOperandType();
				if(operandType.equals( "register" )){
					code = code.concat(regTable.get(instruction.operands[i].name));
				}
				else if (operandType.equals( "immediate" )){
					immLong = instruction.operands[i].extractImmediate();
					tempString = Long.toBinaryString(immLong);
					if(instruction.operator.equals("addi") || instruction.operator.equals("movBNE")
											   || instruction.operator.equals("movei")){	//6 bits
						while (tempString.length() < 6){
							tempString = "0"+tempString;
						}

					}
					else if (instruction.operator.equals("slti")
						|| instruction.operator.equals("srl")){	//2 bits
						while (tempString.length() < 2){
							tempString = "0"+tempString;
						}
					}
					else if (instruction.operator.equals("setBEQ")){	//4 bits
						while (tempString.length() < 4){
							tempString = "0"+tempString;
						}
					}
					else if (instruction.operator.equals("lil") || instruction.operator.equals("liu")){	//9 and 1 bit
						//System.out.println("before: "+tempString);
						if(instruction.operands[i].name.length() > 3){	//9 bits, greater than 3 b/c input as hex
							while (tempString.length() < 9)
								tempString = "0"+tempString;
						}
						//System.out.println("after "+tempString);

					//System.out.println("tempString lil/liu: "+tempString);
					}
					code = code.concat( tempString );
				}
				else if (operandType.equals( "label")){
					//trying to find our destination address so we can use two's complement to
					//calculate where we need to go relative to the program counter (where we are now)
					//System.out.println(instruction.operands[i].name);
					dest = labelTable.findLabelAddr(instruction.operands[i].name);
					//System.out.println("labelLoc "+dest);
					if(dest == -1)
						break;
					dest = dest - programCounter + instruction.operands[i].offset;

					//System.out.println("dest "+dest);
					//System.out.println("PC: "+programCounter);
					//System.out.println("offset "+instruction.operands[i].offset);

					tempString = Integer.toBinaryString(dest);

					//System.out.println("dest: "+tempString);

					if(tempString.length() < 6 && instruction.operator.equals("setBEQ"))
						tempString = "000000"+tempString;
					else if(tempString.length() < 10)
						tempString = "000000000000"+tempString;
					if(instruction.operator.equals("setBEQ"))
						code = code.concat( tempString.substring(tempString.length()-6 ));
					else
						code = code.concat( tempString.substring(tempString.length()-10 ));
					//System.out.println("label: "+code);
				}


			}
			
			if(isOffset){
				//add offset for lw and sw
				switch(offsetInt){
					case 0: offsetString = "00";
							break;
					case 1: offsetString = "01";
							break;
					case 2: offsetString = "10";
							break;
					case 3: offsetString = "11";
							break;
				}
				code = code.concat(offsetString);
			}


			/*
			if(code.length() == 15)
				code = code+"00";
			else if(code.length() == 
			else if (code.length() == 11)
				code = code+"0000";
			*/
			int difference =  17-code.length();

			for (int i = 0; i < difference; i++) {
				code = code + "0";
			}
			//System.out.println("binary: "+code);
			code = this.binaryToHex(code);
			//System.out.println("hex: "+code);
			return code;
	}

    // The student has to implement it for updating the program counter.
    public void updateProgramCounter(Instruction instruction){
		this.programCounter++;
		//System.out.println("pc incremented to "+programCounter);
	}

    // The student has to implement it for initializing some of their own variables and data structures.
    public void initialization()  throws IOException{
		currentSourceCodeline = 0;
		Assembler_MH curr = new Assembler_MH();
		this.setupOpcodeTable();
		this.setupRegisterTable();
		this.getAllLabels();
	}
	
	//Already did this inside generateCode()!
    // The student has to implement it for replacing the labels used in instruction
	//this replaces any label in instruction with the dest. addr
    public void replaceInstructionLabel(Instruction instruction){
	}
	

    // The student has to implement it for replacing the labels used in memory
    public void replaceMemoryLabel(){
		int memLength = memory.length();
		int checkValidity;
		String memData = "";
		String hexString = "";

		for(int i = 0;i<memLength;i++){
			memData = memory.entries[i].data;
			//System.out.println("data: "+memory.entries[i].data);
			//System.out.println("label: "+Integer.toString((labelTable.findLabelAddr(memData))));
			//System.out.println("addr:"+memory.entries[i].address);
			checkValidity = labelTable.findLabelAddr(memData);
			if(checkValidity != -1){
				//System.out.println("check: "+checkValidity);
				hexString = Integer.toHexString(checkValidity);
				hexString = "0x"+hexString;
				memory.entries[i].data = hexString;
				//System.out.println("dataAfter: "+memory.entries[i].data);


			}
			if (memData.length() < 11){

				if( memData.startsWith("0x")){
					memData = memData.substring(2);

					while(memData.length() < 9)
						memData = "0"+memData;
				}
			}
		}
	}
	
	//input should be 17 bit binary instruction, output is hex String
	private String binaryToHex(String binaryCode){
		
		String hexString = "";
		String tempString = "";
		tempString = binaryCode.substring(0,1);
		hexString = hexString.concat(Long.toHexString(Long.parseLong(tempString,2)));

		tempString = binaryCode.substring(1,5);
		hexString = hexString.concat(Long.toHexString(Long.parseLong(tempString,2)));

		tempString = binaryCode.substring(5,9);
		hexString = hexString.concat(Long.toHexString(Long.parseLong(tempString,2)));

		tempString = binaryCode.substring(9,13);
		hexString = hexString.concat(Long.toHexString(Long.parseLong(tempString,2)));

		tempString = binaryCode.substring(13);
		hexString = hexString.concat(Long.toHexString(Long.parseLong(tempString,2)));

		return hexString;
	}

	//goes through entire file to add all labels to labelTable
	private void getAllLabels()		throws IOException
	{
		int currentLine = 0;
		while(sourceFile.ready())	
		{
			String sourceCodeLine = sourceFile.readLine().trim();


      	    if(sourceCodeLine.startsWith("//") || sourceCodeLine.length() == 0 
											   || sourceCodeLine.startsWith("."))
    	    {
    	    	continue;
            }
            // trim the leading spaces and return the source code line.
            sourceCodeLine = sourceCodeLine.trim();
			if(sourceCodeLine.indexOf(":") == -1)
				currentLine++;

			if(this.isLabel(sourceCodeLine)){
				//System.out.println("label: "+extractLabel(sourceCodeLine));
				//System.out.println("  line: "+currentLine);
				labelTable.addLabel(extractLabel(sourceCodeLine), currentLine);
				
			}

		}

		sourceFile.close();
	}

	//table with opcode names and binary codes
	private void setupOpcodeTable(){
		opcodeTable = new HashMap<String,String>();

		//a type
		opcodeTable.put("add", "00000");
		opcodeTable.put("sub", "00001");
		
		//m type
		opcodeTable.put("lw", "00010");
		opcodeTable.put("sw", "00011");
		opcodeTable.put("move", "00100");

		//l type
		opcodeTable.put("srl", "00101");
		opcodeTable.put("nor", "00110");

		//j type
		opcodeTable.put("j", "00111");
		opcodeTable.put("jal", "01000");
		opcodeTable.put("jr", "01001");

		//i type
		opcodeTable.put("addi", "01010");
		opcodeTable.put("slti", "01011");
		opcodeTable.put("movei", "01100");

		//bs type
		opcodeTable.put("in", "01101");
		opcodeTable.put("out", "01110");
		opcodeTable.put("bne", "01111");
		opcodeTable.put("beq", "10000");

		//n type
		opcodeTable.put("addLoad", "10001");
		opcodeTable.put("addBNE", "10010");
		opcodeTable.put("mov45", "10011");
		opcodeTable.put("movBNE", "10100");
		opcodeTable.put("lil", "10101");
		opcodeTable.put("liu", "10110");
		opcodeTable.put("setBEQ", "10111");

		//halt
		opcodeTable.put("halt", "11000");
	}

	//table with register names and codes
	private void setupRegisterTable(){
		regTable = new HashMap<String,String>();
		
		regTable.put("$zero","0000");
		regTable.put("$ra","0001");
		regTable.put("$v0","0010");
		regTable.put("$sp","0011");
		regTable.put("$a0","0100");
		regTable.put("$a1","0101");
		regTable.put("$t0","0110");
		regTable.put("$t1","0111");
		regTable.put("$t2","1000");
		regTable.put("$t3","1001");
		regTable.put("$t4","1010");
		regTable.put("$t5","1011");
		regTable.put("$t6","1100");
		regTable.put("$s0","1101");
		regTable.put("$s1","1110");
		regTable.put("$s2","1111");
	}


	public static void main(String[] args) throws IOException
	{
		Assembler_MH assembler = new Assembler_MH(args);
		assembler.AssembleCode(args);
	}
}


